﻿using System.Collections.Generic;
using System.Linq;
using Godot.core.os;
using Godot.core.io;
using Godot.core.math;

namespace Godot.core
{
    public partial class Globals : Object
    {
        public class Singleton 
        {
		    public StringName name;
		    public Object ptr;
		    public Singleton(StringName p_name , Object p_ptr) 
            { 
                name = p_name; 
                ptr = p_ptr; 
            }
	    }

        protected class _VariantContainer
        {
            public int order;
            public bool persist;
            public Variant variant;
            public bool hide_from_editor;
            public bool overrided;
            public _VariantContainer(Variant p_variant, int p_order, bool p_persist = false) 
            { 
                variant = p_variant; 
                order = p_order; 
                hide_from_editor = false; 
                persist = p_persist; 
                overrided = false; 
            }
        }

        protected struct _VCSort
        {
            public string name;
            public Variant.Type type;
            public int order;
            public int flags;
        }

        protected class _VCSortComparer : IComparer<_VCSort>
        {
            public int Compare(_VCSort x, _VCSort y)
            {
 	            return x.order == y.order ? 
                    x.name.CompareTo(y.name) : 
                    (x.order - y.order);
            }
        }

        public static Globals singleton
        {
            get
            {
                return __singleton;
            }
        }

        public Globals()
        {
            _resource_path = "";
            _props = new Dictionary<StringName, _VariantContainer>();
            _custom_prop_info = new Dictionary<StringName, PropertyInfo>();
            _singletons = new List<Singleton>();
            __singleton = this;
        }

        public void register_global_defaults()
        {
            //nothing
        }

        public Error setup(string p_path)
        {
            if (OS.singleton.get_executable_path() != "")
            {
                if (_load_resource_pack(OS.singleton.get_executable_path()))
                {
                    if (p_path != "")
                    {
                        _resource_path = p_path;
                    }
                    else
                    {
                        DirAccess d = DirAccess.create(DirAccess.AccessType.ACCESS_FILESYSTEM);
                        _resource_path = d.get_current_dir();
                        d = null;
                    }

                    if (_load_settings("res://engine.cfg") == Error.OK || _load_settings_binary("res://engine.cfb") == Error.OK)
                    {
                        _load_settings("res://override.cfg");
                    }
                    return Error.OK;
                }
            }

            //throw new System.NotImplementedException();
            //if (FileAccessNetworkClient::get_singleton()) 
            //{

            //    if (_load_settings("res://engine.cfg")==OK || _load_settings_binary("res://engine.cfb")==OK) {

            //        _load_settings("res://override.cfg");

            //    }

            //    return OK;
            //}

            if (OS.singleton.get_resource_dir() != "")
            {
                //OS will call Globals->get_resource_path which will be empty if not overriden!
                //if the OS would rather use somewhere else, then it will not be empty.
                _resource_path = OS.singleton.get_resource_dir().Replace("\\", "/");
                if (_resource_path.Length > 0 && _resource_path[_resource_path.Length - 1] == '/')
                    _resource_path = _resource_path.Substring(0, _resource_path.Length - 1); // chop end

                //print_line("has res dir: "+resource_path);
                if (!_load_resource_pack("res://data.pck"))
                    _load_resource_pack("res://data.pcz");

                // make sure this is load from the resource path
                //print_line("exists engine cfg? "+itos(FileAccess::exists("/engine.cfg")));
                if (_load_settings("res://engine.cfg") == Error.OK || _load_settings_binary("res://engine.cfb") == Error.OK)
                {
                    //print_line("loaded engine.cfg");
                    _load_settings("res://override.cfg");

                }

                return Error.OK;
            }

            {
                DirAccess d = DirAccess.create(DirAccess.AccessType.ACCESS_FILESYSTEM);

                if (d == null)
                {
                    _resource_path = p_path;
                }
                else
                {
                    d.change_dir(p_path);

                    string candidate = d.get_current_dir();
                    string current_dir = d.get_current_dir();
                    bool found = false;
                    bool first_time = true;

                    while (true)
                    {
                        //try to load settings in ascending through dirs shape!

                        //tries to open pack, but only first time
                        if (first_time && _load_resource_pack(current_dir + "/data.pck"))
                        {
                            if (_load_settings("res://engine.cfg") == Error.OK || _load_settings_binary("res://engine.cfb") == Error.OK)
                            {
                                _load_settings("res://override.cfg");
                                found = true;
                            }
                            break;
                        }
                        else if (_load_settings(current_dir + "/engine.cfg") == Error.OK || _load_settings_binary(current_dir + "/engine.cfb") == Error.OK)
                        {
                            _load_settings(current_dir + "/override.cfg");
                            candidate = current_dir;
                            found = true;
                            break;
                        }

                        d.change_dir("..");
                        if (d.get_current_dir() == current_dir)
                            break; //not doing anything useful
                        current_dir = d.get_current_dir();
                        first_time = false;
                    }


                    _resource_path = candidate;
                    _resource_path = _resource_path.Replace("\\", "/"); // windows path to unix path just in case
                    d = null;

                    if (!found)
                        return Error.ERR_FILE_NOT_FOUND;
                }
            }

            if (_resource_path.Length > 0 && _resource_path[_resource_path.Length - 1] == '/')
                _resource_path = _resource_path.Substring(0, _resource_path.Length - 1); // chop end

            return Error.OK;
        }

        protected Error _load_settings_binary(string p_path)
        {
            Error err = Error.FAILED;
	        FileAccess f= FileAccess.open(p_path, FileAccess.ModeFlags.READ, ref err);
	        if (err != Error.OK) 
            {
		        return err;
	        }


	        byte[] hdr = new byte[4];
	        f.get_buffer(hdr,4);
	        if (hdr[0]!='E'|| hdr[1]!='C' || hdr[2]!='F' || hdr[3]!='G') 
            {
		        //memdelete(f);
		        //ERR_EXPLAIN("Corrupted header in binary engine.cfb (not ECFG)");
		        return Error.ERR_FILE_CORRUPT;
	        }

	        uint count = f.get_32();

	        for(int i = 0; i < count; i++) 
            {
		        uint slen = f.get_32();
                byte[] cs = new byte[slen];
		        f.get_buffer(cs,(int) slen);

                string key = System.Text.Encoding.UTF8.GetString(cs);

		        uint vlen = f.get_32();
		        byte[] d = new byte[vlen];
		        f.get_buffer(d,(int) vlen);
		        
                Variant value = new Variant();
                unsafe
                {
                    fixed (byte* buf = d)
                    {
                        err = Marshalls.decode_variant(ref value, buf, vlen, ref slen);
                    }
                }

		        //ERR_EXPLAIN("Error decoding property: "+key);
		        if(err != Error.OK) continue;
                bool r_valid = false;
                set(key,value, ref r_valid);
		        set_persisting(key, true);
	        }

	        return Error.OK;
        }

        protected Error _load_settings(string p_path)
        {           
            Error err = Error.FAILED;
	        FileAccess f = FileAccess.open(p_path, FileAccess.ModeFlags.READ, ref err);

	        if (err != Error.OK) 
            {
		        return err;
	        }

	        string line = "";
	        string section = "";
	        string subpath = "";

	        int line_count = 0;

	        while(!f.eof_reached()) 
            {
		        line = f.get_line().strip_edges();
		        line_count++;

		        if (line == "")
			        continue;

		        // find comments		         
                {
			        int pos = 0;
			        while (true) 
                    {
				        int ret = line.IndexOf(";", pos);
				        if (ret == -1)
					        break;
				        int qc = 0;
				        for(int i = 0; i < ret; i++) 
                        {
					        if (line[i] == '"')
						        qc++;
				        }

				        if ((qc & 1) == 0) 
                        {
					        //not inside string, real comment
					        line = line.Substring(0,ret);
					        break;
				        }

				        pos = ret + 1;
			        }
		        }

		        if (line.StartsWith("[")) 
                {
			        int end = line.LastIndexOf("]");
			        if(end != line.Length - 1) continue;

			        section = line.Substring(1, line.Length - 2);

                    if (section == "global" || section == "")
                        subpath = "";
                    else
                        subpath = section + "/";
		        } 
                else if (line.IndexOf("=") != -1) 
                {
			        int eqpos = line.IndexOf("=");
			        string var = line.Substring(0, eqpos).strip_edges();
			        string value = line.Substring(eqpos + 1, line.Length).strip_edges();

			        Variant val = _decode_variant(value);

                    bool r_valid = false;
			        set(subpath + var, val, ref r_valid);
			        set_persisting(subpath+var,true);
			        //props[subpath+var]=VariantContainer(val,last_order++,true);
		        } 
                else 
                {
			        if (line.Length > 0) 
                    {
				        //ERR_PRINT(String("Syntax error on line "+itos(line_count)+" of file "+p_path).ascii().get_data());
			        }
		        }
	        }
            f.close();
	        return Error.OK;
        }

        public void set_persisting(string p_name, bool p_persist) 
        {
	        if(!_props.ContainsKey(p_name)) return;
	        _props[p_name].persist = p_persist;
        }

        public bool is_persisting(string p_name)
        {
	        if(!_props.ContainsKey(p_name)) return false;
	        return _props[p_name].persist;
        }

        protected bool _load_resource_pack(string p_pack)
        {
            if (PackedData.singleton.is_disabled())
		        return false;

	        bool ok = PackedData.singleton.add_pack(p_pack) == Error.OK;

	        if (!ok)
		        return false;

	        //if data.pck is found, all directory access will be from here
	        DirAccess.make_default<DirAccessPack>(DirAccess.AccessType.ACCESS_RESOURCES);
	        _using_datapack = true;

	        return true;
        }

        public static Variant global_def(string p_var, Variant p_default) 
        {
            if (Globals.singleton.has(p_var))
		        return Globals.singleton.get(p_var);
	        Globals.singleton.set(p_var,p_default);
	        return p_default;     
        }

        public string get_resource_path()
        {
            return _resource_path;
        }

        public string localize_path(string p_path)
        {
	        if (_resource_path == "")
		        return p_path; //not initialied yet

	        if (p_path.StartsWith("res://"))
		        return p_path.simplify_path();


	        DirAccess dir = DirAccess.create(DirAccess.AccessType.ACCESS_FILESYSTEM);

	        string path = p_path.Replace("\\","/").simplify_path();

	        if (dir.change_dir(path) == Error.OK) 
            {
		        string cwd = dir.get_current_dir();
		        cwd = cwd.Replace("\\","/");

		        Misc.memdelete(dir);

		        if (!cwd.StartsWith(_resource_path)) {
			        return p_path;
		        };

		        return cwd.Replace(_resource_path, "res:/");
	        } 
            else 
            {
		        Misc.memdelete(dir);

		        int sep = path.LastIndexOf("/");
		        if (sep == -1) 
                {
			        return "res://" + path;
		        }
		        string parent = path.Substring(0, sep);

		        string plocal = localize_path(parent);
		        if (plocal == "") 
                {
			        return "";
		        }
		        return plocal + path.Substring(sep, path.Length - sep);
	        }
        }

        public string globalize_path(string p_path)
        {
            if (p_path.StartsWith("res://"))
            {
                if (_resource_path != "")
                {

                    return p_path.Replace("res:/", _resource_path);
                };
                return p_path.Replace("res://", "");
            };

            return p_path;
        }

        public bool has_singleton(string p_name)
        {
	        return get_singleton_object(p_name) != null;
        }

        public Object get_singleton_object(string p_name)
        {
	        foreach(Singleton E in _singletons) 
            {
		        if (E.name == p_name) 
                {
			        return E.ptr;
		        }
	        }

	        return null;
        }

        public void get_singletons(ref List<Singleton> p_singletons) 
        {
	        foreach(Singleton E in _singletons)
		        p_singletons.Add(E);
        }

        public void add_singleton(Singleton singleton)
        {
            _singletons.Add(singleton);
        }

        public bool has(StringName p_key)
        {
            //_THREAD_SAFE_METHOD_
            return _props.ContainsKey(p_key);
        }

        public Error save() 
        {
	        return save_custom(get_resource_path() + "/engine.cfg");
        }

        public void clear(string p_name) 
        {
	        if(!_props.ContainsKey(p_name)) return;
	        _props.Remove(p_name);
        }

        public void set_order(string p_name, int p_order)
        {
	        if(!_props.ContainsKey(p_name)) return;
	        _props[p_name].order = p_order;
        }

        public int get_order(string p_name)
        {
	        if(!_props.ContainsKey(p_name)) return -1;
	        return _props[p_name].order;
        }

        public void set_custom_property_info(string p_prop, PropertyInfo p_info)
        {
            if(!_props.ContainsKey(p_prop)) return;
            _custom_prop_info[p_prop] = p_info;
        }

        internal bool is_using_datapack()
        {
            throw new System.NotImplementedException();
        }

        //--------------------------------------------------------------------------
        protected new bool _set(StringName p_name, Variant p_value) 
        {
	        //_THREAD_SAFE_METHOD_
	
	        if (p_value.type == Variant.Type.NIL)
		        _props.Remove(p_name);
	        else 
            {
		        if (_props.ContainsKey(p_name)) 
                {
			        if (!_props[p_name].overrided)
				        _props[p_name].variant = p_value;
		        } 
                else 
                {
			        _props[p_name] = new _VariantContainer(p_value, _last_order++);
		        }
	        }

	        if (!_disable_platform_override) 
            {
		        string s = p_name;
		        int sl = s.IndexOf("/");
		        int p = s.IndexOf(".");
		        if (p != -1 && sl != -1 && p < sl) 
                {
			        string[] ps = s.Substring(0,sl).Split(new char[] { '.' });
			        string prop = s.Substring(sl, s.Length - sl);
			        for(int i = 1; i < ps.Length; i++) 
                    {
				        if (ps[i] == OS.singleton.get_name()) 
                        {
					        string fullprop = ps[0] + prop;
                            bool r_valid = false;
					        set(fullprop, p_value, ref r_valid);
					        _props[fullprop].overrided = true;
				        }
			        }
		        }

	        }

	        return true;
        }

        protected new bool _get(StringName p_name, ref Variant r_ret)
        {
	        //_THREAD_SAFE_METHOD_

	        if (!_props.ContainsKey(p_name))
		        return false;
	        r_ret = _props[p_name].variant;
	        return true;	
        }
        //--------------------------------------------------------------------------

        protected static Variant _decode_variant(string p_string)
        {
            string str = p_string.strip_edges();

	        if (string.Compare(str, "true", true) == 0)
		        return new Variant(true);
	        
            if (string.Compare(str, "false", true) ==0)
		        return new Variant(false);
	        
            if (string.Compare(str, "nil", true) == 0)
		        return new Variant();
	        
            if (str.is_valid_float()) {
		        if (str.IndexOf(".") == -1)
			        return int.Parse(str);
		        else
			        return double.Parse(str);

	        }
	        if (str.StartsWith("#")) //string
            { 
		        return Color.html(str);
	        }

	        if (str.StartsWith("\"")) //string
            { 
		        int end = str.LastIndexOf("\"");
		        if(end == 0) return new Variant();
		        return str.Substring(1, end - 1).xml_unescape();
	        }

	        if (str.StartsWith("["))//array 
            { 
		        int close_pos = str.LastIndexOf("]");
		        if(close_pos == -1) return new Variant();
		        List<Variant> array  = new List<Variant>();

		        int pos = 1;

		        while(pos < close_pos) 
                {
			        string s = _get_chunk(str, pos, close_pos);
			        array.Add(_decode_variant(s));
		        }
		        return array;

	        }

            if (str.StartsWith("{")) //array
            { 
                int close_pos = str.LastIndexOf("}");
                if(close_pos == -1) return new Variant();
                Dictionary<Variant, Variant> d = new Dictionary<Variant,Variant>();

                int pos = 1;

                while(pos < close_pos) 
                {
                    string key = _get_chunk(str,pos,close_pos);
                    string data = _get_chunk(str,pos,close_pos);
                    d[_decode_variant(key)] = _decode_variant(data);
                }
                return d;
            }

            if (str.StartsWith("key"))
            {
                List<string> pars = _decode_params(p_string);

                if (pars.Count != 1 && pars.Count != 2) return new Variant();

                int scode = 0;

                if (int.TryParse(pars[0], out scode))
                {
                    //do nothing
                }
                else
                {
                    scode = find_keycode(pars[0]);
                }

                InputEvent ie = new InputEvent();
                ie.type = InputEvent.Type.KEY;
                ie.key.scancode = (uint)scode;

                if (pars.Count == 2)
                {
                    string mods = pars[1];
                    if (mods.IndexOf("C", System.StringComparison.OrdinalIgnoreCase) != -1)
                        ie.key.mod.control = true;
                    if (mods.IndexOf("A", System.StringComparison.OrdinalIgnoreCase) != -1)
                        ie.key.mod.alt = true;
                    if (mods.IndexOf("S", System.StringComparison.OrdinalIgnoreCase) != -1)
                        ie.key.mod.shift = true;
                    if (mods.IndexOf("M", System.StringComparison.OrdinalIgnoreCase) != -1)
                        ie.key.mod.meta = true;
                }
                return ie;

            }

            if (str.StartsWith("mbutton")) 
            {
                List<string> pars = _decode_params(p_string);
                if(pars.Count !=2) return new Variant();

                InputEvent ie = new InputEvent();
                ie.type = InputEvent.Type.MOUSE_BUTTON;
                ie.device = int.Parse(pars[0]);
                ie.mouse_button.button_index = int.Parse(pars[1]);
                return ie;
            }

            if (str.StartsWith("jbutton")) 
            {
                List<string> pars = _decode_params(p_string);
                if(pars.Count != 2)  return new Variant();

                InputEvent ie = new InputEvent();
                ie.type = InputEvent.Type.JOYSTICK_BUTTON;
                ie.device = int.Parse(pars[0]);
                ie.joy_button.button_index = int.Parse(pars[1]);

                return ie;
            }

            if (str.StartsWith("jaxis")) 
            {
                List<string> pars = _decode_params(p_string);
                if(pars.Count != 2) return new Variant();

                InputEvent ie = new InputEvent();
                ie.type = InputEvent.Type.JOYSTICK_MOTION;
                ie.device = int.Parse(pars[0]);
                ie.joy_motion.axis = int.Parse(pars[1]);

                return ie;
            }

            if (str.StartsWith("img")) 
            {
                List<string> pars = _decode_params(p_string);
                if (pars.Count == 0) 
                {
                    return new Image();
                }

                if(pars.Count != 5) 
                    return new Image();

                string format = pars[0].strip_edges();

                Image.Format imgformat = Image.Format.FORMAT_RGBA;

                if (format == "grayscale") 
                {
                    imgformat = Image.Format.FORMAT_GRAYSCALE;
                } 
                else if (format=="intensity")
                {
                    imgformat = Image.Format.FORMAT_INTENSITY;
                } 
                else if (format == "grayscale_alpha") 
                {
                    imgformat = Image.Format.FORMAT_GRAYSCALE_ALPHA;
                } 
                else if (format == "rgb") 
                {
                    imgformat = Image.Format.FORMAT_RGB;
                } 
                else if (format == "rgba") 
                {
                    imgformat = Image.Format.FORMAT_RGBA;
                } 
                else if (format == "indexed") 
                {
                    imgformat = Image.Format.FORMAT_INDEXED;
                } 
                else if (format == "indexed_alpha") 
                {
                    imgformat = Image.Format.FORMAT_INDEXED_ALPHA;
                } 
                else if (format == "bc1") 
                {
                    imgformat = Image.Format.FORMAT_BC1;
                } 
                else if (format == "bc2") 
                {
                    imgformat = Image.Format.FORMAT_BC2;
                } 
                else if (format == "bc3") 
                {
                    imgformat = Image.Format.FORMAT_BC3;
                } 
                else if (format == "bc4") 
                {
                    imgformat = Image.Format.FORMAT_BC4;
                } 
                else if (format == "bc5") 
                {
                    imgformat = Image.Format.FORMAT_BC5;
                } 
                else if (format == "custom") 
                {
                    imgformat = Image.Format.FORMAT_CUSTOM;
                } 
                else 
                {
                    return new Variant();
                }

                int mipmaps = int.Parse(pars[1]);
                int w = int.Parse(pars[2]);
                int h = int.Parse(pars[3]);

                if (w == 0 && w == 0) {
                    //r_v = Image(w, h, imgformat);
                    return new Image();
                };

                string data = pars[4];
                int datasize = data.Length / 2;
                byte[] pixels = new byte[datasize];

                int idx = 0;
                byte byte_value = 0;
                while( idx < datasize * 2) 
                {
                    char c = data[idx];

                    if(c == '<') return new Variant();

                    if ( (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f') ) 
                    {
                        if ((idx & 1) != 0) 
                        {
                            byte_value |= hex2char(c);
                            pixels[idx >> 1] = byte_value;
                        } 
                        else 
                        {
                            byte_value =(byte)( hex2char(c) << 4);
                        }

                        idx++;
                    }

                }

                return new Image(w, h, mipmaps,imgformat, pixels);
            }

            if (str.IndexOf(",") != -1)//vector2 or vector3
            { 
                List<float> farr = str.split_floats(",", true);
                if (farr.Count == 2)
                {
                    return new Vector2(farr[0], farr[1]);
                }
                if (farr.Count == 3)
                {
                    return new Vector3(farr[0], farr[1], farr[2]);
                }
                return new Variant();
            }

	        return new Variant();
        }

        private static byte hex2char(char c)
        {
            throw new System.NotImplementedException();
        }

        private static int find_keycode(string p)
        {
            throw new System.NotImplementedException();
        }

        private static List<string> _decode_params(string p_string)
        {
            throw new System.NotImplementedException();
        }

        protected static string _encode_variant(Variant p_variant) 
        {
            throw new System.NotImplementedException();
        }

        protected static string _get_chunk(string str, int pos, int close_pos)
        {
            throw new System.NotImplementedException();
        }

        protected static new void _bind_methods()
        {
            ObjectTypeDB.bind_method(_MD("has", "name"), typeof(Globals).GetMethod("has"));
            ObjectTypeDB.bind_method(_MD("set_order", "name", "pos"), typeof(Globals).GetMethod("set_order"));
            ObjectTypeDB.bind_method(_MD("get_order", "name"), typeof(Globals).GetMethod("get_order"));
            ObjectTypeDB.bind_method(_MD("set_persisting", "name", "enable"), typeof(Globals).GetMethod("set_persisting"));
            ObjectTypeDB.bind_method(_MD("is_persisting", "name"), typeof(Globals).GetMethod("is_persisting"));
            ObjectTypeDB.bind_method(_MD("clear", "name"), typeof(Globals).GetMethod("clear"));
            ObjectTypeDB.bind_method(_MD("localize_path", "path"), typeof(Globals).GetMethod("localize_path"));
            ObjectTypeDB.bind_method(_MD("globalize_path", "path"), typeof(Globals).GetMethod("globalize_path"));
            ObjectTypeDB.bind_method(_MD("save"), typeof(Globals).GetMethod("save"));
            ObjectTypeDB.bind_method(_MD("has_singleton"), typeof(Globals).GetMethod("has_singleton"));
            ObjectTypeDB.bind_method(_MD("get_singleton"), typeof(Globals).GetMethod("get_singleton_object"));
        }

        private static Globals __singleton = null;
        protected List<Singleton> _singletons;
        protected Dictionary<StringName, _VariantContainer> _props;
        protected Dictionary<StringName, PropertyInfo> _custom_prop_info;
        protected string _resource_path;
        protected bool _using_datapack;
        protected bool _disable_platform_override;
        protected int _last_order;

      
    }

    public partial class Globals
    {
        public Error save_custom(string p_path = "", Dictionary<string, Variant> p_in_custom = null, SortedSet<string> p_in_ignore_masks = null)
        {
	        if(p_path == "") return Error.ERR_INVALID_PARAMETER;

            Dictionary<string, Variant> p_custom = p_in_custom ?? new Dictionary<string, Variant>();
            SortedSet<string> p_ignore_masks = p_in_ignore_masks ?? new SortedSet<string>();

            SortedSet<_VCSort> vclist = new SortedSet<_VCSort>(new _VCSortComparer());

            foreach(KeyValuePair<StringName, _VariantContainer> G in _props) 
            {
                _VariantContainer v = G.Value;

                if (v.hide_from_editor)
                    continue;

                if (p_custom.ContainsKey(G.Key))
                    continue;

                bool discard = false;

                foreach(string E in p_ignore_masks) 
                {
                    if ( G.Key == E) 
                    {
                        discard = true;
                        break;
                    }
                }

                if (discard)
                    continue;

                _VCSort vc = new _VCSort();
                vc.name = G.Key;//*k;
                vc.order = v.order;
                vc.type = v.variant.type;
                vc.flags = (int)PropertyUsageFlags.PROPERTY_USAGE_CHECKABLE 
                    | (int)PropertyUsageFlags.PROPERTY_USAGE_EDITOR 
                    | (int)PropertyUsageFlags.PROPERTY_USAGE_STORAGE;

                if (!v.persist)
                    continue;

                vclist.Add(vc);
            }

            foreach(KeyValuePair<string,Variant> E in p_custom) 
            {
                _VCSort vc = new _VCSort();
                vc.name = E.Key;
                vc.order = 0xFFFFFFF;
                vc.type = E.Value.type;
                vc.flags = (int) PropertyUsageFlags.PROPERTY_USAGE_STORAGE;
                vclist.Add(vc);
            }

            Dictionary<string, List<string>> props = new Dictionary<string,List<string>>();

            foreach(_VCSort E in vclist) 
            {
                string category = E.name;
                string name = E.name;

                int div = category.IndexOf("/");

                if (div < 0)
                    category = "";
                else 
                {
                    category = category.Substring(0, div);
                    name = name.Substring(div + 1);
                }
                if (props[category] == null) 
                    props[category] = new List<string>();
                props[category].Add(name);
            }

            if (p_path.StartsWith(".cfg"))
                return _save_settings_text(p_path, props, p_custom);
            else if (p_path.EndsWith(".cfb"))
                return _save_settings_binary(p_path, props, p_custom);
            else 
            {
                //ERR_EXPLAIN("Unknown config file format: "+p_path);
                return Error.ERR_FILE_UNRECOGNIZED;
            }

	        return Error.OK;
        }

        private Error _save_settings_binary(string p_file, Dictionary<string, List<string>> props, Dictionary<string, Variant> p_custom)
        {
            Error err = Error.FAILED;
	        FileAccess file = FileAccess.open(p_file, FileAccess.ModeFlags.WRITE,ref err);
	        if (err != Error.OK) 
            {
		        //ERR_EXPLAIN("Coudln't save engine.cfb at "+p_file);
		        return err;
	        }

	        byte[] hdr = { (byte)'E', (byte)'C', (byte)'F', (byte)'G' };
	        file.store_buffer(hdr, 4);

	        int count = 0;

	        foreach(KeyValuePair<string, List<string>> E in props) 
            {
		        foreach(string F in E.Value) 
                {
			        count++;
		        }
	        }

	        file.store_32((uint)count); //store how many properties are saved

	        foreach(KeyValuePair<string, List<string>> E in props) 
            {
		        foreach(string F in E.Value) 
                {
			        string key = F;
			        if (E.Key != "")
				        key = E.Key + "/" + key;
			        Variant value = new Variant();
			        if (p_custom.ContainsKey(key))
				        value = p_custom[key];
			        else
				        value = get(key);

			        file.store_32((uint)key.Length);
			        file.store_string(key);

			        int len = 0;
                    unsafe
                    {
                        err = Marshalls.encode_variant(value, null, ref len);

                        if (err != Error.OK)
                        {
                            Misc.memdelete(file);
                            return Error.ERR_INVALID_DATA;
                        }


                        byte[] arbuff = new byte[len];

                        fixed (byte* buff = arbuff)
                        {
                            err = Marshalls.encode_variant(value, buff, ref len);
                        }

                        if (err != Error.OK)
                        {
                            Misc.memdelete(file);
                            return Error.ERR_INVALID_DATA;
                        }

                        file.store_32((uint)len);
                        file.store_buffer(arbuff, arbuff.Length);
                    }
		        }
	        }

	        file.close();
	        Misc.memdelete(file);

	        return Error.OK;
        }

        protected Error _save_settings_text(string p_file, Dictionary<string, List<string>> props, Dictionary<string, Variant> p_custom)
        {
            Error err = Error.FAILED;
            FileAccess file = FileAccess.open(p_file, FileAccess.ModeFlags.WRITE, ref err);

            if (err != Error.OK)
            {
                //ERR_EXPLAIN("Coudln't save engine.cfg - "+p_file);
                return err;
            }

            foreach (KeyValuePair<string, List<string>> E in props)
            {
                if (E.Equals(props.First()))
                    file.store_string("\n");

                if (E.Key != "")
                    file.store_string("[" + E.Key + "]\n\n");

                foreach (string F in E.Value)
                {
                    string key = F;
                    if (E.Key != "")
                        key = E.Key + "/" + key;
                    Variant value = new Variant();
                    if (p_custom.ContainsKey(key))
                        value = p_custom[key];
                    else
                        value = get(key);

                    file.store_string(F + "=" + _encode_variant(value) + "\n");

                }
            }

            file.close();
            Misc.memdelete(file);

            return Error.OK;
        }
    }

    public partial class Globals
    {
        #region OBJ_TYPE( Globals, Object )
        /* TYPE API */
        public override string get_type() { return "Globals"; }

        public override string get_save_type() { return get_type(); } //type stored when saving

        public override StringName get_type_name() { return (StringName)get_type_static(); }

        public override bool is_type(string p_type) { return (p_type == "Globals") ? true : base.is_type(p_type); }

        protected override void _initialize_typev() { initialize_type(); }

        protected override bool _setv(StringName p_name, Variant p_property)
        {
            if (base._setv(p_name, p_property))
                return true;

            if (typeof(Globals).GetMethod("_set", Misc.BindingFlags_Greedy) != typeof(Object).GetMethod("_set", Misc.BindingFlags_Greedy))
            {
                return _set(p_name, p_property);
            }
            return false;
        }

        protected override bool _getv(StringName p_name, ref Variant r_property)
        {
            if (typeof(Globals).GetMethod("_get", Misc.BindingFlags_Greedy) != typeof(Object).GetMethod("_get", Misc.BindingFlags_Greedy))
            {
                if (_get(p_name, ref r_property))
                    return true;
            }

            return base._getv(p_name, ref r_property);
        }

        protected override void _get_property_listv(ref List<PropertyInfo> p_list, bool p_reversed)
        {
            if (!p_reversed)
            {
                base._get_property_listv(ref p_list, p_reversed);
            }

            p_list.Add(new PropertyInfo(Variant.Type.NIL, get_type_static(), PropertyHint.PROPERTY_HINT_NONE, "", (int)PropertyUsageFlags.PROPERTY_USAGE_CATEGORY));

            if (!_is_gpl_reversed())
                ObjectTypeDB.get_property_list("Globals", ref p_list, true);

            if (typeof(Globals).GetMethod("_get_property_list", Misc.BindingFlags_Greedy) != typeof(Object).GetMethod("_get_property_list", Misc.BindingFlags_Greedy))
            {
                _get_property_list(ref p_list);
            }

            if (_is_gpl_reversed())
                ObjectTypeDB.get_property_list("Globals", ref p_list, true);

            if (p_reversed)
            {
                base._get_property_listv(ref p_list, p_reversed);
            }
        }

        protected override void _notificationv(int p_notification, bool p_reversed)
        {
            if (!p_reversed)
                base._notificationv(p_notification, p_reversed);

            if (typeof(Globals).GetMethod("_notification") != typeof(Object).GetMethod("_notification"))
            {
                _notification(p_notification);
            }

            if (p_reversed)
                base._notificationv(p_notification, p_reversed);
        }

        public static new string get_type_static() { return "Globals"; }

        public static new string get_parent_type_static() { return Object.get_type_static(); }

        public static new void get_inheritance_list_static(ref List<string> r_inheritance_list)
        {
            Object.get_inheritance_list_static(ref r_inheritance_list);
            r_inheritance_list.Add(get_type_static());
        }

        public static new string get_category_static()
        {
            string category = Object.get_category_static();
            if (typeof(Globals).GetMethod("_get_category") != typeof(Object).GetMethod("_get_category"))
            {
                if (category != "")
                    category += "/";
                category += _get_category();
            }
            return category;
        }

        public static new string inherits_static() { return "Object"; }

        public static new void get_valid_parents_static(ref List<string> p_parents)
        {
            if (typeof(Globals).GetMethod("_get_valid_parents_static") != typeof(Object).GetMethod("_get_valid_parents_static"))
            {
                Globals._get_valid_parents_static(ref p_parents);
            }

            Object.get_valid_parents_static(ref p_parents);
        }

        public static new void initialize_type()
        {
            if (__initialized)
                return;
            Object.initialize_type();
            ObjectTypeDB._add_type<Globals>();
            if (typeof(Globals).GetMethod("_bind_methods", Misc.BindingFlags_Greedy) 
                != typeof(Object).GetMethod("_bind_methods", Misc.BindingFlags_Greedy))
                _bind_methods();
            __initialized = true;
        }

        private static bool __initialized = false;
        #endregion
    }
}
